Data Story: Het verband tussen de levenstandaarden in een land en migratie#
Lars van der Groep 14346168
Guido Elzer 15817628
Simon van Rooij 15692221
Inleiding#
Migratie is al erg lang een erg belangrijk onderwerp waar de meningen verdeeld over zijn. Er zijn namelijk verschillende redenen waardoor mensen uit hun herkomst land vertrekken. Veel mensen en ook overheden zijn geen voorstander van buitenlanders die binnenkomen in hun land, omdat ze gebruik willen maken van het succes dat daar behaald is. In deze datastory wordt er onderzocht of mensen vooral emigreren om uit hun eigen land weg te gaan, om naar een beter land te gaan, of misschien wel een combinatie tussen de twee.
Eerste perspectief:#
Migranten emigreren vooral uit hun eigen land omdat de levensomstandigheden in hun eigen land te slecht/gevaarlijk zijn om een normaal leven in te kunnen hebben.
Argument 1: Landen met een historische gebeurtenis wat de leefomstandigheden onleefbaar maakt hebben een sterke stijging van migratie als gevolg.#
Argument 2: Er is sprake van een zichtbaar verband tussen migratiecijfers een slechte leefomstandigheden maar een slechte leefomstandigheid heeft niet altijd een verhoging van migratie als gevolg.#
Tweede perspectief:#
Migratie heeft niet per se te maken met hoe slecht het met een land gaat maar eerder met hoe goed het met de buurlanden gaat.
Eerste argument: Mortality Rate en Migratie#
Tweede argument: gemiddeld BBP per persoon en emigratiepercentage trendline#
Derde argument: buurlandverschillen#
import pandas as pd
import plotly.express as px
import plotly.graph_objs as go
from plotly.offline import init_notebook_mode
from plotly.subplots import make_subplots
init_notebook_mode(connected=True)
from IPython.display import display, HTML
import json
import uuid
Dataset en Preprocessing#
Om onze argumenten te onderbouwen gebruiken we meerdere datasets die onze punten versterken. De dataset die centraal achter dit onderzoek staat is de dataset over wereldwijde migratie van de World Bank Group(2025) Deze dataset heeft meerdere variabelen die aangegeven zijn in de output hieronder. Alle jaartallen gebruiken data verzameld over een periode van 10 jaar. (Link naar de database https://databank.worldbank.org/source/global-bilateral-migration)
file = r"P_Data_Extract_From_Global_Bilateral_Migration.xlsx"
df = pd.read_excel(file)
df.head()
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
File ~\miniconda3\envs\jupyterbook\lib\site-packages\pandas\compat\_optional.py:135, in import_optional_dependency(name, extra, errors, min_version)
134 try:
--> 135 module = importlib.import_module(name)
136 except ImportError:
File ~\miniconda3\envs\jupyterbook\lib\importlib\__init__.py:126, in import_module(name, package)
125 level += 1
--> 126 return _bootstrap._gcd_import(name[level:], package, level)
File <frozen importlib._bootstrap>:1050, in _gcd_import(name, package, level)
File <frozen importlib._bootstrap>:1027, in _find_and_load(name, import_)
File <frozen importlib._bootstrap>:1004, in _find_and_load_unlocked(name, import_)
ModuleNotFoundError: No module named 'openpyxl'
During handling of the above exception, another exception occurred:
ImportError Traceback (most recent call last)
Cell In[2], line 2
1 file = r"P_Data_Extract_From_Global_Bilateral_Migration.xlsx"
----> 2 df = pd.read_excel(file)
3 df.head()
File ~\miniconda3\envs\jupyterbook\lib\site-packages\pandas\io\excel\_base.py:495, in read_excel(io, sheet_name, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skiprows, nrows, na_values, keep_default_na, na_filter, verbose, parse_dates, date_parser, date_format, thousands, decimal, comment, skipfooter, storage_options, dtype_backend, engine_kwargs)
493 if not isinstance(io, ExcelFile):
494 should_close = True
--> 495 io = ExcelFile(
496 io,
497 storage_options=storage_options,
498 engine=engine,
499 engine_kwargs=engine_kwargs,
500 )
501 elif engine and engine != io.engine:
502 raise ValueError(
503 "Engine should not be specified when passing "
504 "an ExcelFile - ExcelFile already has the engine set"
505 )
File ~\miniconda3\envs\jupyterbook\lib\site-packages\pandas\io\excel\_base.py:1567, in ExcelFile.__init__(self, path_or_buffer, engine, storage_options, engine_kwargs)
1564 self.engine = engine
1565 self.storage_options = storage_options
-> 1567 self._reader = self._engines[engine](
1568 self._io,
1569 storage_options=storage_options,
1570 engine_kwargs=engine_kwargs,
1571 )
File ~\miniconda3\envs\jupyterbook\lib\site-packages\pandas\io\excel\_openpyxl.py:552, in OpenpyxlReader.__init__(self, filepath_or_buffer, storage_options, engine_kwargs)
534 @doc(storage_options=_shared_docs["storage_options"])
535 def __init__(
536 self,
(...)
539 engine_kwargs: dict | None = None,
540 ) -> None:
541 """
542 Reader using openpyxl engine.
543
(...)
550 Arbitrary keyword arguments passed to excel engine.
551 """
--> 552 import_optional_dependency("openpyxl")
553 super().__init__(
554 filepath_or_buffer,
555 storage_options=storage_options,
556 engine_kwargs=engine_kwargs,
557 )
File ~\miniconda3\envs\jupyterbook\lib\site-packages\pandas\compat\_optional.py:138, in import_optional_dependency(name, extra, errors, min_version)
136 except ImportError:
137 if errors == "raise":
--> 138 raise ImportError(msg)
139 return None
141 # Handle submodules: if we have submodule, grab parent module from sys.modules
ImportError: Missing optional dependency 'openpyxl'. Use pip or conda to install openpyxl.
De tweede dataset die we gebruiken is de dataset over de ontwikkeling van landen over tijd aan de hand van levensstandaarden (world Bank Group, 2025). Het succes van een land is hier in veel verschillende methodes af te lezen. Door het grote aantal variabelen hebben we gekozen voor de meest belangrijke variabelen met de grootste hoeveelheid data. Door te kijken naar de stijgingen of dalingen in de dataset kunnen wij concluderen hoe een land zich over een periode van jaren/decennia ontwikkeld. (Link naar de database: https://databank.worldbank.org/source/world-development-indicators)
file2 = r"P_Data_Extract_From_World_Development_Indicators.xlsx"
df = pd.read_excel(file2)
df.head()
| Country Name | Country Code | Series Name | Series Code | 1960 [YR1960] | 1970 [YR1970] | 1980 [YR1980] | 1990 [YR1990] | 2000 [YR2000] | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | Afghanistan | AFG | Access to clean fuels and technologies for coo... | EG.CFT.ACCS.ZS | .. | .. | .. | .. | 5.5 |
| 1 | Afghanistan | AFG | Access to clean fuels and technologies for coo... | EG.CFT.ACCS.RU.ZS | .. | .. | .. | .. | 0.8 |
| 2 | Afghanistan | AFG | Access to clean fuels and technologies for coo... | EG.CFT.ACCS.UR.ZS | .. | .. | .. | .. | 25.3 |
| 3 | Afghanistan | AFG | Access to electricity (% of population) | EG.ELC.ACCS.ZS | .. | .. | .. | .. | 4.4 |
| 4 | Afghanistan | AFG | Access to electricity, rural (% of rural popul... | EG.ELC.ACCS.RU.ZS | .. | .. | .. | .. | .. |
De derde dataset die we gebruiken is de dataset met de inwonersaantallen per land. Aan de hand van deze dataset kunnen we de migratiecijfers vergelijken met de inwonersaantallen van het betreffende land. Dit heeft de voordelen dat inwoner aantallen niet een vertekend beeld geven in het onderzoek (Link naar de database: https://data.worldbank.org/indicator/SP.POP.TOTL)
De laatste datasets die gebruikt worden als ondersteuning van onze perspectieven zijn de datasets “bbb.xlsx, gdp_per_inwoner.xlsx en longitude-latitude.xlsx”. bbb.xlsx is een dataset die de sterftecijfers per land weergeeft over een tijdsperiode. Deze hebben we los gemaakt uit world development indicators.
gdp_per_inwoner.xlsx komt uit de World Bank Group (2025) en geeft het gemiddelde bruto binnenlands product (BBP/GDP) aan per inwoner over een tijdsperiode.
Longitude-latitude.xlsx is een dataset die uit Google Datasets(2025) komt. De dataset geeft de coördinaten van alle landen in de wereld aan.
We hebben onze datasets niet echt verwerkt voordat we gingen werken aan de visualisaties. We hebben dit bij elke visualisatie apart gedaan in plaats van in één keer aan het begin. Dit komt vooral omdat we allemaal appart de data hadden verwerkt voor onze eigen visualisaties.
import pandas as pd
import plotly.graph_objects as go
df = pd.read_excel('P_Data_Extract_From_Global_Bilateral_Migration.xlsx')
df.columns = df.columns.str.strip()
coords = pd.read_csv('longitude-latitude.csv')
coords = coords[['ISO-ALPHA-3', 'Latitude', 'Longitude']].rename(columns={'ISO-ALPHA-3': 'Code'}).drop_duplicates()
df = df.dropna(subset=['Country Origin Name', 'Country Origin Code'])
year_columns = [col for col in df.columns if '[' in col and ']' in col]
for year in year_columns:
df[year] = pd.to_numeric(df[year], errors='coerce').fillna(0)
valid_countries = sorted(df['Country Origin Name'].unique())
fig = go.Figure()
default_country = valid_countries[0] if valid_countries else None
default_year = year_columns[0] if year_columns else None
default_year_str = default_year.split('[')[1].split(']')[0] if default_year else None
for country in valid_countries:
for year_col in year_columns:
year = year_col.split('[')[1].split(']')[0]
country_df = df[df['Country Origin Name'] == country].copy()
country_df = country_df.rename(columns={year_col: 'Migratie'})
country_df = country_df.merge(coords, left_on='Country Origin Code', right_on='Code', how='left')
country_df = country_df.rename(columns={'Latitude': 'Lat_O', 'Longitude': 'Lon_O'}).drop(columns='Code')
country_df = country_df.merge(coords, left_on='Country Dest Code', right_on='Code', how='left')
country_df = country_df.rename(columns={'Latitude': 'Lat_D', 'Longitude': 'Lon_D'}).drop(columns='Code')
country_df = country_df.dropna(subset=['Lat_O', 'Lon_O', 'Lat_D', 'Lon_D'])
lats, lons, names = [], [], []
for _, row in country_df.iterrows():
if float(row['Migratie']) > 0:
lats.extend([row['Lat_O'], row['Lat_D'], None])
lons.extend([row['Lon_O'], row['Lon_D'], None])
names.extend([f"{row['Country Dest Name']} ({int(float(row['Migratie']))})"] * 2 + [None])
fig.add_trace(
go.Scattergeo(
lat=lats,
lon=lons,
hovertext=names,
mode='lines',
line=dict(width=1, color='red'),
name=f"{country} ({year})",
visible=(country == default_country and year == default_year_str)
)
)
def make_visibility(selected_country, selected_year):
visibility = []
for trace in fig.data:
trace_country = trace.name.split(' (')[0]
trace_year = trace.name.split('(')[-1][:-1]
visibility.append(trace_country == selected_country and trace_year == selected_year)
return visibility
combo_buttons = []
for country in valid_countries:
for year_col in year_columns:
year = year_col.split('[')[1].split(']')[0]
visibility = make_visibility(country, year)
combo_buttons.append(
dict(
label=f"{country} - {year}",
method="update",
args=[
{"visible": visibility},
{"title": f"Migratiestromen vanuit {country} in {year}"}
]
)
)
fig.update_layout(
title=f'Migratiestromen vanuit {default_country} in {default_year_str}',
geo=dict(
projection_type='natural earth',
showland=True,
landcolor='rgb(243, 243, 243)',
countrycolor='rgb(204, 204, 204)',
fitbounds="locations",
lataxis_range=[-90, 90],
lonaxis_range=[-180, 180]
),
updatemenus=[
dict(
buttons=combo_buttons,
direction="down",
pad={"r": 10, "t": 10},
showactive=True,
x=0.1,
xanchor="left",
y=1.05,
yanchor="top"
)
],
height=700,
margin=dict(r=0, t=80, l=0, b=0),
dragmode=False
)
fig.show(config={'scrollZoom': False, 'staticPlot': True})